/**
 * Created by OJesusO on 2017/9/27.
 * 上传工具 - 核心
 * @param {Object} conf  [配置信息] {
 *      debug:      是否开启debug，1:开启 0:关闭
 *      inputId:    文件域Id，无法获取对象时将自动创建Element
 *      dragBoxId:  拖拽释放块Id，空字符串表示不进行拖拽监听，没有对应的Id元素将为body设置
 *      uploadURL:  上传URL地址
 *      queueLen:   上传队列长度 - 同时上传数量
 *      multiple:   是否开启多文件上传，1:开启 0:关闭
 *      tagName:    标签名称 - 用于区分不同类型的上传
 *      csrftoken:  csrftoken 值
 *      fileType:   文件类型限制，留空为不限制 - 例，['image', 'video'] - mime type 前缀，'image/png'|'video/mp4'
 *      fileSuffix: 文件后缀限制，留空为不限制 - 例，{'image':['png'], 'video':['mp4']} - mime type 前缀作为 key
 *      md5ChunkSize:   md5分块大小，默认6291456（6MB）
 *
 *      changeInputEven:    文件域chang事件回调
 *      dragFileEven:   拖动文件事件回调
 *      dropFileEven:   释放文件事件回调
 *
 *      initpostBeforeCallback: 初始化上传前回调 - initpostBeforeCallback(file); - 返回值将存到data中的beforeData
 *      getMd5ingCallback:  获取md5中回调 - getMd5ingCallback(file, loaded, total);
 *      waitingCallback:    等待上传中回调 - waitingCallback(data);
 *      startingCallback:   开始上传中回调 - startingCallback(data);
 *      progressCallback:   上传传输中回调 - progressCallback(data, loaded, total);
 *      successCallback:    上传成功后回调 - successCallback(data);
 *      failCallback:       上传失败后回调 - failCallback(data);
 *      endedCallback:      结束上传后回调 - endedCallback(data);
 *      errorCallback:      异常错误后回调 - errorCallback(pos, code, info, file);
 *                          (流程位置, 错误码, 错误信息, 异常文件) - (1, -1, '', file)
 *                          流程位置: 1:数据校验，2:数据初始化，3:传输异常，4:上传异常
 *                          错误码: -101:文件类型异常，-102:文件后缀异常，
 *                                 -201:获取文件md5失败
 * }
 * @method [方法使用] {
 *      openSelect: 打开文件域进行选择，MyUploads.openSelect()
 *      postFiles:  上传文件调用， MyUploads.openSelect(files) - files:文件对象数组
 *      getQueue:   获取队列，MyUploads.getQueue(end, success) - end:是否已经结束，success:是否已经完成
 * }
 * @require [额外要求] {
 *      TOOL - (each, extend, inArray, isArray, jsonParse, isFunction, getObjLength) - 功能核心实现
 *      SparkMD5 - 文件md5加密工具
 *      FileReader - html5 fileReader api
 *  }
 * @author OJesusO
 * @last 2017-12-15
 */
(function(TOOL, doc, W){
    //默认配置
    var CONF = {
        debug: 1,
        inputId: 'uploads-core-file',
        dragBoxId: 'uploads-core-dragBox',
        uploadURL: '',
        queueLen: 1,
        multiple: 1,
        tagName: 'default',
        csrftoken: '',
        fileType: null,
        fileSuffix: null,
        md5ChunkSize: 6291456,

        changeInputEven: function(){
            this.postFiles(this.InputObj.files);
            this.InputObj.value = '';   //防止点击取消触发change事件
        },
        dragFileEven: function(){},
        dropFileEven: function(files){
            this.postFiles(files);
        },

        initpostBeforeCallback: null,
        getMd5ingCallback: null,
        waitingCallback: null,
        startingCallback: null,
        progressCallback: null,
        successCallback: null,
        failCallback: null,
        endedCallback: null,
        errorCallback: null,
    },
    console = W.console;

    //主方法
    function Uploads (conf) {
        var _OBJ = this,
        _Queue = false;

        _OBJ.CONF = false;
        _OBJ.InputObj = false;
        _OBJ.DragBox = false;

        _OBJ.openSelect = function () {
            _OBJ.InputObj.click();
        };
        _OBJ.postFiles = function (files) {
            var chunkSize = _OBJ.getConfig('md5ChunkSize'),
            initpostBeforeCallback = _OBJ.getConfig('initpostBeforeCallback'),
            getMd5ingCallback = _OBJ.getConfig('getMd5ingCallback');
            TOOL.each(files, function(i, file){
                var beforeData = initpostBeforeCallback(file);
                if (!_OBJ.isAllowUploads(file)) {
                    return ;
                }
                calcfileMd5(file, function(md5){
                    var data = _OBJ.initPost(file, md5, beforeData);
                    if (data) {
                        _Queue.addQueue(data);
                    }
                }, {
                    chunkSize: chunkSize,
                    progressCallback: function(loaded, total){
                        getMd5ingCallback.bind(_OBJ)(file, loaded, total);
                    },
                    errorCallback: function(){
                        _OBJ.getConfig('errorCallback')(2, -201, '获取文件md5失败', file);
                    },
                });
            });
        };
        _OBJ.getQueue = function (end, success) {
            return _Queue.getQueue(end, success);
        };

        (function(){
            _OBJ.initConf(CONF, conf);
            _Queue = new _OBJ.Queue(_OBJ);
            _OBJ.setInputObj();
            _OBJ.setDragBox();
            _OBJ.listenEven();
        })();
    };


    //设置prototype
    (function(UPR){
        UPR.Queue = function (_OBJ) {
            var Queue = [], Success = [], Fail = [], Uping = {};
            maxLength = _OBJ.getConfig('queueLen'),
            waitingCallback = _OBJ.getConfig('waitingCallback'),
            startingCallback = _OBJ.getConfig('startingCallback'),
            successCallback = _OBJ.getConfig('successCallback'),
            failCallback = _OBJ.getConfig('failCallback'),
            endedCallback = _OBJ.getConfig('endedCallback');

            this.addQueue = function (data) {
                var onload = data.xhr.onload,
                    onerror = data.xhr.onerror;
                data.xhr.onload = function () {
                    if (onload()) {
                        Success.push(data);
                        successCallback(data);
                        endedCallback(data);
                    } else {
                        Fail.push(data);
                        failCallback(data);
                        endedCallback(data);
                    }
                };
                data.xhr.onerror = function () {
                    onerror(); Fail.push(data);
                    failCallback(data);
                    endedCallback(data);
                };
                data.xhr.onloadend = function () {
                    delete Uping[data._id]; isNext();
                    console.log('onloadend:', data);
                };
                Queue.push(data);
                waitingCallback(data);
                isNext();
            };
            this.getQueue = function (end, success) {
                end = end || false;
                success = success || false;
                return end ? (success ? Success : Fail) : Queue;
            };

            function isNext () {
                if (TOOL.getObjLength(Queue) > 0 && TOOL.getObjLength(Uping) < maxLength) {
                    var next = Queue.shift();
                    Uping[next._id] = next;
                    next.xhr.send(next.form);
                    startingCallback(next)
                    return true;
                } else {
                    return false;
                }
            };
        };
        UPR.initConf = function (CONF, conf) {
            this.CONF = TOOL.extend(true, {}, CONF, conf);
            var callbacks = [
                'initpostBeforeCallback', 'getMd5ingCallback', 'waitingCallback',
                'startingCallback', 'progressCallback', 'successCallback',
                'failCallback', 'endedCallback', 'errorCallback',
            ];
            for (var i in callbacks) {
                this.initCallback(callbacks[i]);
            }
            if (!this.getConfig('debug')) {
                console.log = function(){};
            }
        };
        UPR.initCallback = function (callback) {
            var fun = this.getConfig(callback);
            if (TOOL.isFunction(fun)) {
                this.CONF[callback] = fun.bind(this);
            } else {
                this.CONF[callback] = function(){
                    var args = [callback+':'];
                    for (var i=0; i<arguments.length; i++) {
                        args.push(arguments[i]);
                    }
                    console.log.apply(console, args);
                }.bind(this);
            }
        };
        UPR.initPost = function (file, md5, beforeData) {
            var data = new FormData();
            data.append(this.InputObj.name, file);
            data.append('file_md5', md5);
            data.append('file_size', file.size);
            data.append('input_name', this.getConfig('inputId'));
            if (beforeData.postData) {
                for (var i in beforeData.postData) {
                    data.append(i, beforeData.postData[i]);
                }
            }
            data = {
                _id: getFileId(md5),
                _md5: md5,
                _tag: this.getConfig('tagName'),
                _status: -1,
                file: file,
                form: data,
            };
            data.xhr = this.getRequest(data);
            data.beforeData = beforeData;
            return data;
        };
        UPR.isAllowUploads = function (file) {
            var error = this.getConfig('errorCallback'),
            fileType = this.getConfig('fileType'),
            fileSuffix = this.getConfig('fileSuffix'),
            type = file.type.split('/')[0],
            suffix = file.name.split('.').pop();

            if (fileType && !TOOL.inArray(type, fileType)) {
                error(1, -101, '文件类型异常', file);
                return false;
            }
            if (fileSuffix && TOOL.isArray(fileSuffix[type])
                           && !TOOL.inArray(suffix, fileSuffix[type])) {
                error(1, -102, '文件后缀异常', file);
                return false;
            }
            return true;
        };
        UPR.getConfig = function (key, dft) {
            var keys = TOOL.isArray(key) ? key : key.split('.');
            if (TOOL.getObjLength(keys) > 1) {
                key = keys.pop();
                var data = this.getConfig(keys, dft);
                if (!data || typeof(data[key]) === 'undefined' || data[key] === null) {
                    data = this.getConfig(keys, true);
                    if (!data || typeof(data[key]) === 'undefined' || data[key] === null) {
                        return null;
                    }
                }
                return data[key];
            } else {
                var conf = dft === true ? CONF : this.CONF;
                if (typeof(conf[key]) === 'undefined' || conf[key] === null) {
                    return dft === true ? null : this.getConfig(key, true);
                } else {
                    return conf[key];
                }
            }
        };
        UPR.getRequest = function (data) {
            var xhr = getRequest(),
            csrftoken = this.getConfig('csrftoken'),
            progressCallback = this.getConfig('progressCallback');

            xhr.open("post", this.getConfig('uploadURL'), true);
            xhr.upload.onprogress = function(e){
                //e.loaded:该文件上传了多少  e.total:该文件的总共大小
                progressCallback(data, e.loaded, e.total);
            };
            xhr.onload = function(){
                data.response = TOOL.jsonParse(data.xhr.responseText) || {};
                if (data.response.status > 0) {
                    data._status = 1;
                    return true;    //返回操作成功
                } else {
                    data._status = 0;
				            return false;   //返回操作失败
                }
            }.bind(this);
            xhr.onerror = function(){
                data._status = 0;
                return false;       //返回操作失败
            };
            if (csrftoken) {
                xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
            return xhr;
        };
        UPR.listenEven = function () {
            var changeInputEven = this.getConfig('changeInputEven'),
            dragFileEven = this.getConfig('dragFileEven'),
            dropFileEven = this.getConfig('dropFileEven');

            this.InputObj.addEventListener('change', function(){
                changeInputEven.bind(this)();
            }.bind(this), false);
            this.DragBox.addEventListener('dragover', function(evt){
                evt.stopPropagation();
                evt.preventDefault();
                evt.dataTransfer.dropEffect = 'copy';
                dragFileEven.bind(this)();
            }.bind(this), false);
            this.DragBox.addEventListener('drop', function(evt){
                evt.stopPropagation();
                evt.preventDefault();
                dropFileEven.bind(this)(evt.dataTransfer.files);
            }.bind(this), false);
        };
        UPR.setInputObj = function () {
            var inputId = this.getConfig('inputId'),
                multiple = this.getConfig('multiple');
            this.InputObj = doc.getElementById(inputId);
            if (!this.InputObj) {
                this.InputObj = createInput(inputId, multiple);
            }
            this.InputObj.setAttribute('name', inputId);
        };
        UPR.setDragBox = function () {
            var dragBoxId = this.getConfig('dragBoxId');
            this.DragBox = doc.getElementById(dragBoxId);
            if (!this.DragBox) {
                this.DragBox = doc.querySelector('body');
                this.DragBox.id = dragBoxId;
            }
        };

        function getFileId (md5) {
            return 'file_' + (md5 ? md5 : new Date().getTime());
        };
        function createInput (inputId, multiple) {
            var input = doc.createElement('input');
            input.setAttribute('type', 'file');
            if (multiple) {
                input.setAttribute('multiple', 'multiple');
            }
            input.id = inputId;
            input.style.display = 'none';
            doc.querySelector('body').appendChild(input);
            return input;
        };
        function getRequest () {
            if (window.XMLHttpRequest) {
                return new XMLHttpRequest();
            } else {
                return new ActiveXObject("Microsoft.XMLHTTP");
            }
        };
    })(Uploads.prototype);


    //计算文件md5值
    function calcfileMd5 (file, callback, conf) {
        var blobSlice = false,
        Spark = false,
        Reader = false,
        nowChunk = 0,
        chunkNum = 0,
        chunkSize = 6291456, // Read in chunks of 6MB
        progressCB = null,
        errorCB = null;

        function calcMd5 () {
            chunkNum = Math.ceil(file.size / chunkSize);
            Reader.onload = function(e) {
                Spark.append(e.target.result); // Append array buffer

                nowChunk++;
                progressCB(nowChunk, chunkNum);
                if (nowChunk < chunkNum) {
                    loadChunk();
                } else {
                    callback(Spark.end());
                }
            };
            Reader.onerror = function(){
                errorCB('Reader error');
            };
            loadChunk();
        };
        function loadChunk () {
            var start = nowChunk * chunkSize,
                end = start + chunkSize;
            if (end >= file.size) {
                end = file.size;
            }
            Reader.readAsArrayBuffer(blobSlice.call(file, start, end));
        };
        function getSlice () {
            return File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
        };

        (function(){
            conf = conf || {};
            blobSlice = getSlice();
            Spark = new SparkMD5.ArrayBuffer();
            Reader = new FileReader();
            chunkSize = conf.chunkSize===0 ? file.size : (conf.chunkSize || chunkSize);
            callback = TOOL.isFunction(callback) ? callback : function(md5){};
            progressCB = TOOL.isFunction(conf.progressCallback) ? conf.progressCallback : function(loaded, total){};
            errorCB = TOOL.isFunction(conf.errorCallback) ? conf.errorCallback : function(){};
            calcMd5();
        })();
    };


    //推出类
    W.UploadsCore = Uploads;
})(TOOL, document, window);
